﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Common;
using System.Xml.Serialization;
using System.Threading;

namespace Site_Extractor
{
	[XmlInclude(typeof(Rule))]
	[XmlRoot("GenericForum", IsNullable = false, Namespace = "")]
	public class GenericForum
	{
		public static readonly int MaxNumberOfThreads = 100;
		public string Name;
		public string MainLink { get { return _mainLink; } set { _mainLink = value; } }

		private string _mainLink;
		public ForumRule 		
			RuleForumIdentifier, 
			RuleMessageLinks, 
			RuleMessageAuthor, 
			RuleMessageTopic, 
			RuleMessageText, 
			RuleMessageDate,
			RuleMessageId,
			RuleDownloadMessage;

		public GenericForum() { }

		public List<string> GetAllForumLinks()
		{
			if (RuleForumIdentifier == null || _mainLink == string.Empty) return new List<string>();
			var res = ForumHandler.GatherAllLinks(_mainLink, isForumMine);
			return res;
		}
		private bool isForumMine(string link)
		{
			return (bool)ForumRule.RunRule(RuleForumIdentifier, link);
		}

		public List<ForumMessageInfo> GetAllMessageLinks(string forumLink)
		{
			if (RuleMessageLinks == null) return new List<ForumMessageInfo>();

			var links = ForumRule.RunRule(RuleMessageLinks, forumLink) as List<ForumMessageInfo>;
			foreach (ForumMessageInfo info in links)
			{
				if (info.Link.StartsWith("http")) info.OriginalLink = info.Link;
				else info.OriginalLink = forumLink;
			}
			return links;
		}
		public ForumMessage GetMessage(string msgLink)
		{
			ForumMessage msg = new ForumMessage();
			if (RuleDownloadMessage != null)
			{
				try { msgLink = (string)ForumRule.RunRule(RuleDownloadMessage, msgLink); }
				catch { return msg; }
			}

			try { msg.Author = (string)ForumRule.RunRule(RuleMessageAuthor, msgLink); }
			catch { }
			try { msg.Topic = (string)ForumRule.RunRule(RuleMessageTopic, msgLink); }
			catch { }
			try { msg.Text = (string)ForumRule.RunRule(RuleMessageText, msgLink); }
			catch { }
			try { msg.Date = (DateTime)ForumRule.RunRule(RuleMessageDate, msgLink); }
			catch { }
			try { msg.UniqueId = long.Parse(ForumRule.RunRule(RuleMessageId, msgLink) as string); }
			catch { }

			return msg;
		}
		public bool SaveToXml(string path)
		{
			try
			{
				XmlClassHandler<GenericForum>.Save(this, path + "\\"+ this.Name + ".xml");
				return true;
			}
			catch { return false; }
		}
		public static GenericForum LoadFromXml(string filename)
		{
			try
			{
				return XmlClassHandler<GenericForum>.Load(filename);
			}
			catch { return null; }
		}
		public List<ForumMessage> ExtractForum(int maxThreads, DateTime startDate)
		{
			
			#region Prepage Data Structures
			int totalForumLinks;
			List<ForumMessage> allMsgs = new List<ForumMessage>();
			List<ForumMessageInfo> msgInfos = new List<ForumMessageInfo>();
			if (maxThreads <= 0 || maxThreads > MaxNumberOfThreads) return allMsgs;
			GenericForum f = this;
			Console.WriteLine("-----------------{0}----------------", f.Name);
			var fLinks = f.GetAllForumLinks();
			Console.WriteLine("{0} forum links found", fLinks.Count);
			totalForumLinks = fLinks.Count;
			Thread[] threads = new Thread[maxThreads]; 
			#endregion
			#region Gather All Message Infos
			Console.WriteLine("Starting all threads ...");
			for (int i = 0; i < threads.Length; i++)
			{
				threads[i] = new Thread(new ParameterizedThreadStart(GetMessageLinks));
				threads[i].Name = "Thread #" + i.ToString();
				threads[i].Start(new object[] { f, fLinks, msgInfos });
			}

			int running = 1;
			while (running != 0)
			{
				running = 0;
				foreach (Thread t in threads)
				{
					if (t == null) continue;
					if (t.ThreadState == ThreadState.Running || (t.ThreadState == ThreadState.WaitSleepJoin))
					{ running++; }
				}
				int msgCount = msgInfos.Count;
				int forumLinksCount = fLinks.Count;
				var pct = (int)((double)(totalForumLinks - forumLinksCount-running)*100 / totalForumLinks);
				Console.WriteLine("{2:D2}% done, {0} forums left for scan ({1} threads running)", forumLinksCount, running, pct);
				if (running == 0) break;
				Thread.Sleep(5000);
			}
			var found = from mi in msgInfos
						where mi.Date >= startDate
						select mi;
			var ignored = msgInfos.Count - found.Count();
			msgInfos = new List<ForumMessageInfo>(found);
			Console.WriteLine("part 1/2 done, {0} messages found ({1} ignored)", msgInfos.Count, ignored);
			var totalMsgInfos = msgInfos.Count;
			#endregion
			#region Gather All Messages
			Console.WriteLine("Starting all threads ... (part 2/2)");
			for (int i = 0; i < threads.Length; i++)
			{
				threads[i] = new Thread(new ParameterizedThreadStart(GetMessages));
				threads[i].Name = "Thread #" + i.ToString();
				threads[i].Start(new object[] { f, msgInfos, allMsgs });
			}
			running = 1;
			while (running!=0)
			{
				running = 0;
				foreach (Thread t in threads)
				{
					if (t == null) continue;
					if (t.ThreadState == ThreadState.Running || (t.ThreadState == ThreadState.WaitSleepJoin))
					{ running++; }
				}
				int count = msgInfos.Count;
				var pct = (int)((double)(totalMsgInfos - count-running) * 100 / totalMsgInfos);
				Console.WriteLine("{2:D}% done, {0} messages left for scan ({1} threads running)", count, running, pct);
				if (running == 0) break;
				Thread.Sleep(5000);
			} 
			#endregion
			Console.WriteLine("--------------JOB DONE--------------");
			return allMsgs;
		}
		private void GetMessageLinks(object obj)
		{
			try
			{
				var objs = obj as object[];
				var forum = objs[0] as GenericForum;
				var forumLinks = objs[1] as List<string>;
				var msgInfos = objs[2] as List<ForumMessageInfo>;
				var gatheredLinks = new List<ForumMessageInfo>();
				while (true)
				{
					string link=string.Empty;
					lock (forumLinks)
					{ 
						if (forumLinks.Count == 0) break;
						link = forumLinks[0];
						forumLinks.RemoveAt(0);						
					}

					if (link == string.Empty) break;
					var msgLinks = forum.GetAllMessageLinks(link);
					if (msgLinks != null) gatheredLinks.AddRange(msgLinks);
				}
				lock (msgInfos)
				{
					msgInfos.AddRange(gatheredLinks);
				}
			}
			catch (Exception ex)
			{
				Console.WriteLine("Error in {0}:{1}", Thread.CurrentThread.Name, ex.Message);
			}
		}
		private void GetMessages(object obj)
		{
			try
			{
				var objs = obj as object[];
				var forum = objs[0] as GenericForum;
				var msgInfos = objs[1] as List<ForumMessageInfo>;
				var allMsgs = objs[2] as List<ForumMessage>;
				var gatheredMessages = new List<ForumMessage>();
				while (true)
				{
					ForumMessageInfo msgInfo;
					lock (msgInfos)
					{
						if (msgInfos.Count == 0) break;
						msgInfo = msgInfos[0];
						msgInfos.RemoveAt(0);
					}

					if (msgInfo == null) break;
					gatheredMessages.Add(forum.GetMessage(msgInfo.Link));
				}
				lock (msgInfos)
				{
					allMsgs.AddRange(gatheredMessages);
				}
			}
			catch (Exception ex)
			{
				Console.WriteLine("Error in {0}:{1}", Thread.CurrentThread.Name, ex.Message);
			}
		}
	}
}
